#include <string.h>
#include "alp_string.h"
#include "bencode.h"

static char * parse_string(size_t *size, const char *buf, size_t len)
{
	size_t n;
	char *p = (char *)memchr(buf, ':', len);
	if (p == 0)
		return 0;
	n = p - buf;
	if (n >= len)
		return 0;
	if (alp_strntoz(size, buf, n))
		return 0;
	len -= (++p - buf);
	if (len < *size)
		return 0;
	return p;	
}

alp_status_t bencode_parse(bencode_node_t **node, const char *buf, size_t *len, alp_pool_t *pool)
{
	bencode_node_t *new_node = (bencode_node_t *)alp_palloc(pool, sizeof(bencode_node_t));
	*node = 0;
	if (new_node == 0)
		return ENOMEM;
	switch (*buf)
	{
	case 'd':
	{
		alp_dict_t *dict;
		bencode_node_t *child;
		size_t klen;
		const char *key;
		ALP_CHECK(alp_dict_create(&dict, pool));
		new_node->type = BENCODE_DICT;
		new_node->dict = dict;
		for (++buf, --*len; *buf != 'e'; )
		{
			size_t n;
			if (*len == 0)
				return EILSEQ;
			key = parse_string(&klen, buf, *len);
			if (key == 0)
				return EILSEQ;
			*len -= (key + klen - buf);
			buf = key + klen;
			n = *len;
			ALP_CHECK(bencode_parse(&child, buf, len, pool));
			buf += (n - *len);
			ALP_CHECK(alp_dict_set(dict, key, klen, child));
		}
		--*len; // skip 'e'
		break;
	}
	case 'l':
	{
		alp_list_t *list;
		bencode_node_t *child;
		ALP_CHECK(alp_list_create(&list, pool));
		new_node->type = BENCODE_LIST;
		new_node->list = list;
		for (++buf, --*len; *buf != 'e'; )
		{
			size_t n = *len;
			if (n == 0)
				return EILSEQ;
			ALP_CHECK(bencode_parse(&child, buf, len, pool));
			buf += (n - *len);
			ALP_CHECK(alp_list_append(list, child));
		}
		--*len; // skip 'e'
		break;
	}
	case 'i':
	{
		size_t size;
		const char *p = (const char *)memchr(++buf, 'e', --*len);
		if (p == 0)
			return EILSEQ;
		size = p - buf;
		new_node->type = BENCODE_INT;
		new_node->buffer = alp_memdup(buf, size, pool);
		new_node->length = size;
		*len -= (size + 1); // skip 'e'
		break;
	}
	default:
	{
		size_t size;
		char *p = parse_string(&size, buf, *len);
		if (p == 0)
			return EILSEQ;
		new_node->type = BENCODE_STRING;
		new_node->buffer = alp_memdup(p, size, pool);
		new_node->length = size;
		*len -= (p + size - buf);
		break;
	}
	}
	*node = new_node;
	return 0;
}

#define BENCODE_CALLBACK(buf, len) ALP_CHECK(callback(userdata, buf, len))

static alp_status_t dump_string(const char *buf, size_t len, void *userdata, alp_status_t (*callback)(void *userdata, const char *buf, size_t len))
{
	if (len == 0)
		return callback(userdata, "0:", 2);
	else
	{
		char s[64];
		char *p = s + sizeof(s);
		size_t n;
		for (n = len; n; n /= 10)
			*--p = (char)(n % 10) + '0';
		BENCODE_CALLBACK(p, s + sizeof(s) - p);
		BENCODE_CALLBACK(":", 1);
		BENCODE_CALLBACK(buf, len);
	}
	return 0;
}

alp_status_t bencode_dump(bencode_node_t *node, void *userdata, alp_status_t (*callback)(void *userdata, const char *buf, size_t len))
{
	switch (node->type)
	{
	case BENCODE_INT:
		BENCODE_CALLBACK("i", 1);
		BENCODE_CALLBACK(node->buffer, node->length);
		BENCODE_CALLBACK("e", 1);
		break;
	case BENCODE_STRING:
	{
		ALP_CHECK(dump_string(node->buffer, node->length, userdata, callback));
		break;
	}
	case BENCODE_LIST:
	{
		alp_list_index_t *li;
		BENCODE_CALLBACK("l", 1);
		for (li = alp_list_first(node->list); li; li = alp_list_next(li))
		{
			bencode_node_t *child;
			alp_list_this(li, (void **)&child);
			ALP_CHECK(bencode_dump(child, userdata, callback));
		}
		BENCODE_CALLBACK("e", 1);
		break;
	}
	case BENCODE_DICT:
	{
		alp_pool_t *pool;
		alp_dict_index_t *di;
		BENCODE_CALLBACK("d", 1);
		ALP_CHECK(alp_pool_create(&pool, 0, 0));
		for (di = alp_dict_first(node->dict, pool); di; di = alp_dict_next(di))
		{
			const char *key;
			size_t klen;
			bencode_node_t *child;
			alp_dict_this(di, (const void **)&key, &klen, (void **)&child);
			ALP_CHECK(dump_string(key, klen, userdata, callback));
			ALP_CHECK(bencode_dump(child, userdata, callback));
		}
		BENCODE_CALLBACK("e", 1);
		alp_pool_destroy(pool);
		break;
	}
	default:
		return EILSEQ;
	}
	return 0;
}

#undef BENCODE_CALLBACK
